package UDPTools;

import UDPTools.Settings.UDPSettings;
import Messages.Exceptions.MessageCreationException;
import Messages.Exceptions.MessageLengthException;
import TCPTools.ClientTCP;
import TCPTools.ServeurTCP;
import Model.FileId;
import Model.PathFile;
import Model.Status;
import UDPTools.Exceptions.UDPSenderException;
import UDPTools.Interface.UDPSenderInterface;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import messages.Bye;
import messages.ChatMessage;
import messages.Composing;
import messages.FileRequest;
import messages.FileResponse;
import messages.Hello;
import messages.MessagesFactory;
import messages.Presence;
import messages.Wizz;

/**
 * La classe UDPSender prend en charge les connexions UDP sortantes.
 * Elle émet tous les messages du systeme local vers le réseau. 
 * UDPListener implemente l'interface <code>UDPSenderInterface</code>
 * 
 * <p>Toutes les methodes heritées de l'interface UDPSenderInterface lèvent 
 * une exception de type  <tt>UDPSenderException</tt>
 * 
 * @author      Frank Djomo
 * @author      Christelle SIAGO HAPPI
 * @author      djomopoueme@gmail.com
 * @author      christelle.siagohappi@gmail.com
 * @version     1.0
 * @see     UDPSenderInterface
 * @see     UDPSenderException
 */
public class UDPSender implements UDPSenderInterface{
	
    private DatagramSocket socketUdp;
    private int portEnvoi;
    private int portDistant;
    /**
     * Contructeur 
     * @throws UDPSenderException
     */
    public UDPSender () throws UDPSenderException{
        this(UDPSettings.UDP_PORT_ENVOI,UDPSettings.UDP_PORT_ECOUTE);	
    }
    /**
     * Contructeur
     * @param envoi Port d'envoi
     * @param distant Port Distant
     * @throws UDPSenderException
     */
    public UDPSender (int envoi,int distant) throws UDPSenderException{
        portEnvoi=envoi;
        portDistant=distant;
        try {
            //socketUdp = new DatagramSocket(UDPSettings.UDP_PORT_ENVOI,InetAddress.getLocalHost());
            socketUdp = new DatagramSocket(portEnvoi);
        } catch (SocketException e) {
                throw new UDPSenderException("Erreur lors de la creation du socket dedie aux envoi udp",e);
        }	
    }
    /**
     * Gère l'envoi d'un signal Hello pour la notification de connexion.
     * Lors de la connexion d'un utilisateur, un message <i>Hello Request</i> est diffusé en
     * broadcast sur le réseau pour notifier la connexion aux autres utilisateurs.
     * L'utilisateur attend un Hello Reply pour confirmer la notification.
     * @see Hello
     * @param login     Login de l'utilisateur local
     * @param request   Demande de réponse <code>true</code> 
     * @throws UDPSenderException 
     */
    @Override
    public void sendHello(String login, boolean request) throws UDPSenderException{
        try {
            System.out.println(login+ "> UDPSender("+portEnvoi+","+portDistant+") envoi un Hello "+request);
            byte[] myHelloByteArray = MessagesFactory.getHello(login, request);
            if (!UDPSettings.checkByteMessage(myHelloByteArray)) 
                throw new UDPSenderException("UDP bufferoverflow");    			
            //DatagramPacket packet = new DatagramPacket(messageByte, messageByte.length,InetAddress.getByName(UDPSettings.NETWORK_BROADCAST), UDPSettings.UDP_PORT_ECOUTE);
            DatagramPacket packet=new DatagramPacket(myHelloByteArray, myHelloByteArray.length,InetAddress.getByName(UDPSettings.NETWORK_BROADCAST), portDistant);
            socketUdp.send(packet);
        } catch (MessageCreationException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			

        } catch (UnknownHostException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			
        }
        catch (IOException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			
        }     
    }
     /**
    * Gère l'envoi d'un signal Hello pour confirmation une notification de connexion.
    * L'utilisateur qui reçoit un <i>Hello Request</i> repond en unicast avec message
    * <i>Hello Reply</i>. 
    * @see Hello 
    * @param login      Login de l'utilisateur local
    * @param rAdress    Adresse IP de l'utilisateur distant destinataire du Hello Reply
    * @param reply      Demande de réponse <code>false</code>
    * @throws UDPSenderException 
    */
    @Override
    public void sendHelloUnicast(String login, InetAddress rAdress, boolean reply) throws UDPSenderException {
        try {
            
            System.out.println(login+ "> UDPSender("+portEnvoi+","+portDistant+") envoi un Hello unicast "+reply);

            byte[] myHelloByteArray = MessagesFactory.getHello(login, reply);
            if (!UDPSettings.checkByteMessage(myHelloByteArray)) 
                throw new UDPSenderException("UDP bufferoverflow");    			

            //DatagramPacket packet = new DatagramPacket(messageByte, messageByte.length,InetAddress.getByName(UDPSettings.NETWORK_BROADCAST), UDPSettings.UDP_PORT_ECOUTE);
            DatagramPacket packet=new DatagramPacket(myHelloByteArray, myHelloByteArray.length,rAdress, portDistant);
            socketUdp.send(packet);
        } catch (MessageCreationException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			

        } catch (UnknownHostException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			
        }
        catch (IOException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			
        }    
    }
     /**
    * Gère l'envoi d'un signal Bye pour la notification de deconnexion.
    * @see Bye 
    * @param login      Login de l'utilisateur local
    * @throws UDPSenderException 
    */
    @Override
    public void sendBye(String login) throws UDPSenderException{
        try {
            System.out.println(login+ "> UDPSender("+portEnvoi+","+portDistant+") envoi un Bye en broadcast");

            byte[] myByeArray = MessagesFactory.getBye(login);
            if (!UDPSettings.checkByteMessage(myByeArray)) 
                throw new UDPSenderException("UDP bufferoverflow");    			

            //DatagramPacket packet = new DatagramPacket(messageByte, messageByte.length,InetAddress.getByName(UDPSettings.NETWORK_BROADCAST), UDPSettings.UDP_PORT_ECOUTE);
            DatagramPacket packet=new DatagramPacket(myByeArray, myByeArray.length,InetAddress.getByName(UDPSettings.NETWORK_BROADCAST), portDistant);
            socketUdp.send(packet);
        } catch (MessageCreationException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
           throw new UDPSenderException(ex.getMessage());    			

        } catch (UnknownHostException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			
        }
        catch (IOException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			
        } 
    }
    /**
    * Gère l'envoi d'un signal Wizz à un utlisateur distant.
    * @see Wizz
    * @param login      Login de l'utilisateur local
    * @param rAdress    Adresse IP de l'utilisateur distant destinataire du Wizz
    * @throws UDPSenderException 
    */
    @Override
    public void sendWizz( String login,InetAddress rAdress) throws UDPSenderException {
           try {
            System.out.println(login+ "> UDPSender("+portEnvoi+","+portDistant+") envoi un Wizz à la machine "+rAdress);

            byte[] myWizzArray = MessagesFactory.getWizz(login);
            if (!UDPSettings.checkByteMessage(myWizzArray)) 
                throw new UDPSenderException("UDP bufferoverflow");    			

            //DatagramPacket packet = new DatagramPacket(messageByte, messageByte.length,InetAddress.getByName(UDPSettings.NETWORK_BROADCAST), UDPSettings.UDP_PORT_ECOUTE);
            DatagramPacket packet=new DatagramPacket(myWizzArray, myWizzArray.length,rAdress, portDistant);
            socketUdp.send(packet);
        } catch (MessageCreationException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
                        throw new UDPSenderException(ex.getMessage());    			

        } catch (UnknownHostException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			
        }
        catch (IOException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			
        }
    }
    /**
    * Gère l'envoi d'un signal ChatMessage à un utilisateur distant
    * @see ChatMessage
    * @param login          Login de l'utilisateur local
    * @param rAdress        Adresse IP de l'utilisateur distant destinataire du ChatMessage
    * @param messageText    Message texte à envoyer
    * @throws UDPSenderException 
    */
    @Override
    public void sendChatMessage(String login,InetAddress rAdress, String messageText) throws UDPSenderException {
        try {
            
            System.out.println(login+ "> UDPSender("+portEnvoi+","+portDistant+") envoi un message à la machine "+rAdress);

            byte[] myChatArray = MessagesFactory.getChatMessage(login, messageText);
            if (!UDPSettings.checkByteMessage(myChatArray)) 
                throw new UDPSenderException("UDP bufferoverflow");    			
            //DatagramPacket packet = new DatagramPacket(messageByte, messageByte.length,InetAddress.getByName(UDPSettings.NETWORK_BROADCAST), UDPSettings.UDP_PORT_ECOUTE);
            DatagramPacket packet=new DatagramPacket(myChatArray, myChatArray.length,rAdress, portDistant);
            socketUdp.send(packet);
        } catch (MessageCreationException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
                        throw new UDPSenderException(ex.getMessage(),ex);    			
        } catch (MessageLengthException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage(),ex);
        } catch (UnknownHostException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage(),ex);    			
        }
        catch (IOException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage(),ex);    			
        }
    }
     /**
    * Gère l'envoi d'un signal FileRequest à un utilisateur distant
    * @see FileRequest
    * @param login          Login de l'utilisateur local
    * @param rAdress        Adresse IP de l'utilisateur distant
    * @param file           Nom du fichier à envoyer
    * @param fileId         Identifiant du fichier à envoyer
    * @param size           taille du fichier à envoyer
    * @throws UDPSenderException 
    */
    @Override
    public void sendFileRequest(String login,InetAddress rAdress, PathFile file, FileId fileId, long size) throws UDPSenderException {
         try {
            
            System.out.println(login+ "> UDPSender("+portEnvoi+","+portDistant+") envoi un FileRequest à la machine "+rAdress);

            byte[] myRequestArray = MessagesFactory.getFileRequest(login, file, fileId, size);
            if (!UDPSettings.checkByteMessage(myRequestArray)) 
                throw new UDPSenderException("UDP bufferoverflow");    			
            //DatagramPacket packet = new DatagramPacket(messageByte, messageByte.length,InetAddress.getByName(UDPSettings.NETWORK_BROADCAST), UDPSettings.UDP_PORT_ECOUTE);
            DatagramPacket packet=new DatagramPacket(myRequestArray, myRequestArray.length,rAdress, portDistant);
            socketUdp.send(packet);
        } catch (MessageCreationException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
                        throw new UDPSenderException(ex.getMessage());    			

        } catch (UnknownHostException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			
        }
        catch (IOException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			
        }
    }
    /**
    * Gère l'envoi d'un signal FileResponse à un utilisateur distant
    * @see FileResponse
    * @see FileId
    * 
    * @param login          Login de l'utilisateur local
    * @param rAdress        Adresse IP de l'utilisateur distant 
    * @param jAccept        Reponse à la demande de transfert
    *                      <code>true</code> si utilisateur accepte de recevoir un fichier
    *                      <code>false</code> sinon
    * @param request        FileRequest asscoié à l'utilisateur distant
    * @throws UDPSenderException 
    */
    @Override
    public void sendFileResponse(String login,InetAddress rAdress, boolean jAccept,FileRequest request) throws UDPSenderException {
         
        Random rand = new Random();
        int portTransfert = rand.nextInt(UDPSettings.TCP_PORT_MAX - UDPSettings.TCP_PORT_MIN + 1) + UDPSettings.TCP_PORT_MIN; 
        if(jAccept){            
          Thread t = (new Thread(new ServeurTCP(portTransfert,request.getName(),request.getSize())));
          t.start();
        }
        
        try {
            
            System.out.println(login+ "> UDPSender("+portEnvoi+","+portDistant+") envoi un FileResponse à la machine "+rAdress);

            byte[] myResponseArray = MessagesFactory.getFileResponse(login, jAccept,portTransfert, request.getFileId());
            if (!UDPSettings.checkByteMessage(myResponseArray)) 
                throw new UDPSenderException("UDP bufferoverflow");    			
            //DatagramPacket packet = new DatagramPacket(messageByte, messageByte.length,InetAddress.getByName(UDPSettings.NETWORK_BROADCAST), UDPSettings.UDP_PORT_ECOUTE);
            DatagramPacket packet=new DatagramPacket(myResponseArray, myResponseArray.length,rAdress, portDistant);
            socketUdp.send(packet);
        } catch (MessageCreationException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
                        throw new UDPSenderException(ex.getMessage());    			

        } catch (UnknownHostException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			
        }
        catch (IOException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			
        }
      
    }
     /**
    * Gère l'envoi d'un fichier à un utilisateur distant
    * @see PathFile
    * @param addressServer        Adresse IP du serveur TCP de transfert
    * @param portServer           port de connexion au serveur
    * @param pFileToSend          Nom du fichier à envoyer  
    * @throws UDPSenderException    
    */
     @Override
    public void sendFileData(InetAddress addressServer, int portServer,PathFile pFileToSend) throws UDPSenderException {
        //TODO lancement du client TCP
       Thread t = (new Thread(new ClientTCP(addressServer,portServer,pFileToSend)));
       t.start();
    }
     /**
    * Gère l'envoi d'un signal Presence pour la notification de status
    * @see Presence
    * @param login          Login de l'utilisateur local
    * @param show           Status de l'utilisateur
    * @throws UDPSenderException    
    */
    @Override
    public void sendPresence(String login, Status show) throws UDPSenderException {
        try {
                    
                    System.out.println(login+ "> UDPSender("+portEnvoi+","+portDistant+") envoi une Presence à "+login);

                    byte[] myPresenceArray = MessagesFactory.getPresence(login, show);
                    if (!UDPSettings.checkByteMessage(myPresenceArray)) 
                        throw new UDPSenderException("UDP bufferoverflow");    			
                    //DatagramPacket packet = new DatagramPacket(messageByte, messageByte.length,InetAddress.getByName(UDPSettings.NETWORK_BROADCAST), UDPSettings.UDP_PORT_ECOUTE);
                    DatagramPacket packet=new DatagramPacket(myPresenceArray, myPresenceArray.length,InetAddress.getByName(UDPSettings.NETWORK_BROADCAST), portDistant);
                    socketUdp.send(packet);
                } catch (MessageCreationException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
                        throw new UDPSenderException(ex.getMessage());    			

        } catch (UnknownHostException ex) {
                    Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
                    throw new UDPSenderException(ex.getMessage());    			
                }
                catch (IOException ex) {
                    Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
                    throw new UDPSenderException(ex.getMessage());    			
                }    
        }
    /**
    * Gère l'envoi d'un signal Composing pour la notification d'ecriture 
    * @see Composing
    * @param login          Login de l'utilisateur local
    * @param rAdress        Adresse IP de l'utilisateur distant 
    * @param isWriting   <code>true</code> si utilisateur ecrit 
    *                  <code>false</code> sinon
    */
    @Override
    public void sendComposing(String login, InetAddress rAdress,boolean isWriting) throws UDPSenderException {
        try {
            
            System.out.println(login+ "> UDPSender("+portEnvoi+","+portDistant+") envoi une notification "+isWriting);

            byte[] myComposingArray = MessagesFactory.getComposing(login,isWriting);
            if (!UDPSettings.checkByteMessage(myComposingArray)) 
                throw new UDPSenderException("UDP bufferoverflow");    			
            //DatagramPacket packet = new DatagramPacket(messageByte, messageByte.length,InetAddress.getByName(UDPSettings.NETWORK_BROADCAST), UDPSettings.UDP_PORT_ECOUTE);
            DatagramPacket packet=new DatagramPacket(myComposingArray, myComposingArray.length,rAdress, portDistant);
            socketUdp.send(packet);
        } catch (MessageCreationException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
                        throw new UDPSenderException(ex.getMessage());    			

        } catch (UnknownHostException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			
        }
        catch (IOException ex) {
            Logger.getLogger(UDPSender.class.getName()).log(Level.SEVERE, null, ex);
            throw new UDPSenderException(ex.getMessage());    			
        }       
    }

}

